﻿using Android.Graphics;
using System;
using System.Collections;

namespace Game
{
    public class MainGame
    {
        private int[,] _arr;
        private Random _genRandom;
        private Hashtable _paintMap;
        private Paint _defaultPaint;
        private ArrayList _history;
        private int _allowBackCount;
        private string _allowBackStr = "〄①②③";

        public MainGame()
        {
            InitArray();
            InitColor();
        }

        private void InitColor()
        {
            _paintMap = new Hashtable();
            int[,] colors = new int[11, 4] {
                { 2, 255, 0, 255 },
                { 4, 128, 0, 0 },
                { 8, 0, 128, 0 },
                { 16, 0, 0, 128 },
                { 32, 128, 128, 128 },
                { 64, 0, 0, 0 },
                { 128, 255, 0, 0 },
                { 256, 0, 255, 0 },
                { 512, 0, 0, 255 },
                { 1024, 255, 128, 0 },
                { 2048, 0, 255, 255 }
            };
            for (int i = 0; i < 11; i++)
            {
                Color color = Color.Argb(255, colors[i, 1], colors[i, 2], colors[i, 3]);
                _paintMap[colors[i, 0]] = new Paint
                {
                    Color = color, 
                    TextSize = 80,
                    TextAlign = Paint.Align.Center
                };
            }
            _defaultPaint = new Paint
            {
                Color = Color.Gray,
            };
            _defaultPaint.TextAlign = Paint.Align.Center;
            _defaultPaint.TextSize = 80;
        }

        private void InitArray()
        {
            _history = new ArrayList();
            _allowBackCount = 3;
            _arr = new int[4, 4];
            _genRandom = new Random();

            //Init array values
            for (var k = 0; k < 4; k++) // Column
            {
                for (var j = 0; j < 4; j++) // Rows
                {
                    _arr[k, j] = 0;
                }
            }

            //Generate 2 numbers randomly
            var newRanCoordinate = Random();
            _arr[newRanCoordinate[0, 0], newRanCoordinate[0, 1]] = 2;
            newRanCoordinate = Random();
            _arr[newRanCoordinate[0, 0], newRanCoordinate[0, 1]] = 2;
        }

        private bool MoveGridUp(bool ranTwice)
        {
            var moved = false;

            for(var k = 3; k >= 0; k--)
            {
                for(var j = 3; j >= 0; j--)
                {
                    if (j + 1 < 4)
                    {
                        if (ranTwice == false && (_arr[k, j] == _arr[k, j + 1] || _arr[k, j] == 0))
                        {
                            _arr[k, j] += _arr[k, j + 1];
                            _arr[k, j + 1] = 0;
                            moved = true;
                        }

                        if(ranTwice && _arr[k, j] == 0)
                        {
                            _arr[k, j] += _arr[k, j + 1];
                            _arr[k, j + 1] = 0;
                            moved = true;
                        }
                    }
                }
            }
            return moved;
        }

        private bool MoveGridDown(bool ranTwice)
        {
            var moved = false;

            for (var k = 0; k < 4; k++)
            {
                for (var j = 0; j < 4; j++)
                {
                    if (j - 1 >= 0)
                    {
                        if (ranTwice == false && (_arr[k, j] == _arr[k, j - 1] || _arr[k, j] == 0))
                        {
                            _arr[k, j] += _arr[k, j - 1];
                            _arr[k, j - 1] = 0;
                            moved = true;
                        }

                        if(ranTwice && _arr[k, j] == 0)
                        {
                            _arr[k, j] += _arr[k, j - 1];
                            _arr[k, j - 1] = 0;
                            moved = true;
                        }
                    }
                }
            }
            return moved;
        }

        private bool MoveGridLeft(bool ranTwice)
        {
            var moved = false;

            for (var k = 3; k >= 0; k--)
            {
                for (var j = 3; j >= 0; j--)
                {
                    if (k - 1 >= 0)
                    {
                        if (ranTwice == false && (_arr[k - 1, j] == _arr[k, j] || _arr[k - 1, j] == 0))
                        {
                            _arr[k - 1, j] += _arr[k, j];
                            _arr[k, j] = 0;
                            moved = true;
                        }

                        if(ranTwice && _arr[k - 1, j] == 0)
                        {
                            _arr[k - 1, j] += _arr[k, j];
                            _arr[k, j] = 0;
                            moved = true;
                        }
                    }
                }
            }
            return moved;
        }

        private bool MoveGridRight(bool ranTwice)
        {
            var moved = false;

            for (var k = 0; k < 4; k++)
            {
                for (var j = 0; j < 4; j++)
                {
                    if (k + 1 < 4)
                    {
                        if (ranTwice == false && (_arr[k + 1, j] == _arr[k, j] || _arr[k + 1, j] == 0))
                        {
                            _arr[k + 1, j] += _arr[k, j];
                            _arr[k, j] = 0;
                            moved = true;
                        }

                        if(ranTwice && _arr[k + 1, j] == 0)
                        {
                            _arr[k + 1, j] += _arr[k, j];
                            _arr[k, j] = 0;
                            moved = true;
                        }
                    }
                }
            }
            return moved;
        }

        private int[,] Random()
        {
            var size = 0;
            //Store empty positions in a list
            var emptyPost = new ArrayList();

            //Loop through all to find empty places
            for(var k = 0; k < 4; k++)
            {
                for(var j = 0; j < 4; j++)
                {
                    if (_arr[k, j] != 0) continue;
                    emptyPost.Add(new Position(k, j));
                    size++;
                }
            }

            var values = new int[1, 2];
            var pos = _genRandom.Next(size);
            try
            {
                Position item = (Position)emptyPost[pos];
                values[0, 0] = item.PosX;
                values[0, 1] = item.PosY;
            }
            catch(ArgumentOutOfRangeException)
            {
                values[0, 0] = -1;
                values[0, 1] = -1;
            }

            return values;

        }

        public void Display(Canvas canvas, int windowWidth, int windowHeight)
        {
            Paint blackPaint = new Paint
            {
                Color = Color.Black,
            };

            blackPaint.TextSize = 80;
            string data = "〇〄↑↓←→";
            for (int i = 0; i < 6; i++)
            {
                string keyword = data[i] + "";
                if (i == 1)
                {
                    keyword = _allowBackStr[_allowBackCount] + "";
                }
                canvas.DrawText(keyword, i * windowWidth / 6 + 50, windowHeight - 280, blackPaint);
                canvas.DrawLine(i * windowWidth / 6, windowHeight - 400, i * windowWidth / 6, windowHeight, blackPaint);                
            }
            canvas.DrawLine(0, windowHeight - 400, windowWidth, windowHeight - 400, blackPaint );


            int xlength = (windowWidth - 100) / 4;
            int ylength = (windowHeight - 500) / 4;
            Paint paint = _defaultPaint;
            for (int i=0;i<4; i++)
            {
                for(int j=0;j<4;j++)
                {
                    int posX = i * xlength + 150;
                    int posY = j * ylength + 200;
                    if (_paintMap.Contains(_arr[i, j]))
                    {
                        paint = (Paint)_paintMap[_arr[i, j]];
                    }
                    else
                    {
                        paint = _defaultPaint;
                    }
                    canvas.DrawText(_arr[i, j] + "", posX, posY, paint);
                }
            }
        }

        private void CheckFinalOrGenerateNumber()
        {
            var newRanCoordinate = Random();
            if (newRanCoordinate[0, 0] == -1 && newRanCoordinate[0, 1] == -1)
            {
                
            }
            else
            {
                _arr[newRanCoordinate[0, 0], newRanCoordinate[0, 1]] = 2;
                saveArr();
            }
        }

        private void saveArr()
        {
            int[,] arr = new int[4, 4];

            for (var k = 0; k < 4; k++) // Column
            {
                for (var j = 0; j < 4; j++) // Rows
                {
                    arr[k, j] = _arr[k, j];
                }
            }
            _history.Add(arr);
            if (_allowBackCount < 3)
            {
                _allowBackCount++;
            }
        }

        private void loadBefore()
        {
            if (_history.Count > 1 && _allowBackCount > 0)
            {
                int[,] arr = (int[,])_history[_history.Count - 2];
                for (var k = 0; k < 4; k++) // Column
                {
                    for (var j = 0; j < 4; j++) // Rows
                    {
                        _arr[k, j] = arr[k, j];
                    }
                }
                _history.RemoveAt(_history.Count - 1);
                _allowBackCount--;
            }
        }


        public void DoAction(int action)
        {
            switch (action)
            {
                case 0:
                    InitArray();
                    break;
                case 1:
                    loadBefore();
                    break;
                case 2:// Key.Up
                    if (MoveGridUp(false) &&
                        MoveGridUp(true)) //Repeats the loop to avoid having numbers stay behind
                    {              //This is a fix albeit not a very good one computationally speaking
                        CheckFinalOrGenerateNumber();
                    }
                    break;
                case 3: // Key.Down
                    if (MoveGridDown(false) &&
                        MoveGridDown(true)) //Repeats the loop to avoid having numbers stay behind
                    {                //This is a fix albeit not a very good one computationally speaking
                        CheckFinalOrGenerateNumber();
                    }
                    break;
                case 4: // Key.Left
                    if (MoveGridLeft(false) &&
                        MoveGridLeft(true)) //Repeats the loop to avoid having numbers stay behind
                    {                   //This is a fix albeit not a very good one computationally speaking

                        CheckFinalOrGenerateNumber();
                    }
                    break;
                case 5: // Key.Right
                    if (MoveGridRight(false) &&
                        MoveGridRight(true)) //Repeats the loop to avoid having numbers stay behind
                    {                    //This is a fix albeit not a very good one computationally speaking
                        CheckFinalOrGenerateNumber();
                    }
                    break;
            }
        }
    }

    class Position
    {
        public int PosX;
        public int PosY;
        public Position(int x, int y)
        {
            this.PosX = x;
            this.PosY = y;
        }
    }
}
